#include "BootLoader.h"
uint8_t temp_buff[512] = {0}; 
//所有以_开头的函数均是本文件的内部函数
//为了不重复文件系统挂载、释放、打开文件、关闭文件 等等，所以将这些操作封装在装饰器中，并统一执行器的函数格式
/**
* @brief : static FRESULT _BLT_AppFileDecorator(BLTAppFile *p_app_file, FRESULT (*func)(BLTAppFile *p_app_file))
* @param : p_app_file : 指向BLTAppFile结构体的指针,
* @param : func : 执行器函数，传入的函数指针，函数参数为BLTAppFile结构体指针
* @attention : 这个函数的功能类似python的装饰器，用于处理文件系统挂载、释放、打开文件、关闭文件等操作。并在其中执行对应的函数
* @author : Sheep
* @date : 24/10/25
*/
static FRESULT _BLT_AppFileDecorator(BLTAppFile *p_app_file, FRESULT (*func)(BLTAppFile *p_app_file))
{
    FRESULT res = FR_OK;
    //检查必要的文件路径和文件系统挂载、释放函数是否为NULL
    if(p_app_file->bin_path == NULL || p_app_file->fs_built == NULL || p_app_file->fs_destruct == NULL)
    {
        res = FR_INVALID_NAME;
        BLT_LOG_INFO("path or built or destruct func is NULL");
        TER_LOG_INFO("path or built or destruct func is NULL");
        return res;
    }
    res = p_app_file->fs_built();
    if(res != FR_OK)//文件系统挂载失败
    {
        BLT_LOG_INFO("fs built failed");
        return res;
    } 
        res = f_open(&(p_app_file->app_file), (const TCHAR *)p_app_file->bin_path, FA_OPEN_EXISTING | FA_READ);
        if (res !=  FR_OK)//APP文件打开失败
        {
            BLT_LOG_INFO("Open app file failed");
            return res;
        }
        res = func(p_app_file); //执行执行器函数
        if (res !=  FR_OK) //执行器执行失败
        {
            BLT_LOG_INFO("func failed");
            return res;
        }
        res = f_close(&(p_app_file->app_file));
        if (res !=  FR_OK) //文件关闭失败
        {
            BLT_LOG_INFO("Close app file failed");
            return res;
        }
    res = p_app_file->fs_destruct();
    if(res != FR_OK)//文件系统释放失败
    {
        BLT_LOG_INFO("fs built failed");
        return res;
    } 
    return FR_OK; //如果都没有问题，返回OK
}
// static FRESULT _BLT_GetAppInfo(BLTAppFile *p_app_file) 内部函数，不含文件打开操作，只包含获取文件大小和version
static FRESULT _BLT_GetAppInfo(BLTAppFile *p_app_file)
{
    FILINFO fno;
    uint32_t bw = 0;
    FRESULT res = FR_OK;
    /* 读取固件大小 */ //一般能够打开文件读取大小不会有问题，因此不检查返回值
    f_stat((const TCHAR *)(p_app_file->bin_path), &fno);
    p_app_file->bin_size = (uint32_t)fno.fsize;
    BLT_LOG_INFO("APP firmware size: %d B", p_app_file->bin_size);
    TER_LOG_INFO("APP size: %d B",p_app_file->bin_size);
    /* 读取固件版本 */
    f_lseek(&(p_app_file->app_file), APP_VERSION_ADD_OFFSET);
    res =  f_read(&(p_app_file->app_file), &(p_app_file->bin_version), sizeof(p_app_file->bin_version), &bw); 
    if(res != FR_OK)
    {
        BLT_LOG_INFO("Read app version failed");
        return res;
    }
    BLT_LOG_INFO("APP firmware version: V%X.%02X", (p_app_file->bin_version)>>8, (p_app_file->bin_version)&0xFF);
    TER_LOG_INFO("APP version: V%X.%02X", (p_app_file->bin_version)>>8, (p_app_file->bin_version)&0xFF);
    f_lseek(&(p_app_file->app_file), 0); //归位指针
    return res;
}

// static FRESULT _BLT_GetAppCRC(BLTAppFile *p_app_file) 内部函数，不含文件打开操作， 获取APP文件CRC校验
static FRESULT _BLT_GetAppCRC(BLTAppFile *p_app_file)
{
    FRESULT res = FR_OK;
    uint32_t bw = 0;
    uint32_t TotalSize = 0; //用于计数文件是否读取完成
    if(p_app_file->bin_size > APP_MAX_SIZE_KB || p_app_file->bin_size == 0)     
    {//检查APP_Size是否合法
        res = FR_DENIED;
        BLT_LOG_INFO("APP Size is illegal");
        TER_LOG_INFO("APP Size is illegal");
        return res; 
    }
    MX_CRC_Init(); //初始化CRC校验
    __HAL_CRC_DR_RESET(&hcrc); //重置CRC
    for(;;)
    {
        /* 读取一个扇区的数据到buf */
        res = f_read(&(p_app_file->app_file), &temp_buff, sizeof(temp_buff), &bw);
        /* 读取出错或者读取完毕，退出 */
        if ((res != FR_OK)||bw == 0)
        {
            if(res!= FR_OK){
                BLT_LOG_INFO("read app file failed!");
                TER_LOG_INFO("read app file failed!");
            }
            //如果时因为读取失败退出，给一个提示
            break;
        }
        TotalSize += bw; //记录进度
        //同时进行CRC校验
        for(uint32_t index = 0U; index < (bw/4); index++)
        {
            uint32_t data = *(uint32_t*)(temp_buff+(index*4));
            if(TotalSize == (p_app_file->bin_size) && index == (bw/4 - 1))
            {//app固件的最后一个字为自带的CRC校验值
                p_app_file->bin_crc = data; //取出自带的CRC校验值
                BLT_LOG_INFO("Actual CRC_val = 0x%x",data);
                TER_LOG_INFO("Actual CRC_val\n = 0x%x",data);
            }
            else  hcrc.Instance->DR = data; //其他字都要进行CRC校验
        }
    }
    f_lseek(&(p_app_file->app_file), 0); //每次读取完毕后都归位指针
    p_app_file->bin_cal_crc = hcrc.Instance->DR; //取出计算的CRC值
    BLT_LOG_INFO("Calculate CRC_val = 0x%x",p_app_file->bin_cal_crc);
    TER_LOG_INFO("Cal CRC_val\n = 0x%x",p_app_file->bin_cal_crc);
    HAL_CRC_MspDeInit(&hcrc);//关闭CRC校验
    return res;
}

// static FRESULT _BLT_LoadApp(BLTAppFile *p_app_file)内部函数，不含文件打开操作， 将APP下载到flash中
static FRESULT _BLT_LoadApp(BLTAppFile *p_app_file)
{
    FRESULT res = FR_OK;
    uint32_t bw = 0;
    uint32_t TotalSize = 0; //用于计数写进Flash的大小
    uint16_t count_of_buff = 0; //一个app文件中含有的buff的个数
    float finish_pecent = 0; //复制进度百分比
    uint8_t ter_pro_count = 10; //进度台显示控制
    char str_buff[32] = {0};  //进度台打印数据暂存
    if(p_app_file->bin_size > APP_MAX_SIZE_KB || p_app_file->bin_size == 0)     
    {//检查APP_Size是否合法
        res = FR_DENIED;
        BLT_LOG_INFO("APP Size is illegal");
        return res; 
    }
    // //解除扇区写入的上限
    // CpuFlashErr state = CPUFlash_ChangeUpLimitAddr(APP_START_ADDRESS+APP_MAX_SIZE_KB); 
    // if(state != CFE_OK){res = FR_WRITE_PROTECTED; return res;}//返回写保护
    CpuFlashErr state = CFE_OK;
    for(;;)
    {
        /* 读取一个扇区的数据到buf */
        res = f_read(&(p_app_file->app_file), &temp_buff, sizeof(temp_buff), &bw);
        /* 读取出错或者读取完毕，退出 */
        if ((res != FR_OK)||bw == 0)
        {
            if(res!= FR_OK){//如果时因为读取失败退出，给一个提示
                BLT_LOG_INFO("read failed!");
                TER_LOG_INFO("read failed!");
            }
            break;
        }
        /* 编程内部Flash */
        DISABLE_INT(); //关闭全局中断
        // state = CPUFlash_Write((uint32_t)(APP_START_ADDRESS + count_of_buff*sizeof(temp_buff)),
        //         (uint8_t *)temp_buff, bw);
        state = CPUFLash_LoadCode((uint32_t)(APP_START_ADDRESS + count_of_buff*sizeof(temp_buff)),
                    (uint8_t *)temp_buff, bw);
        /* 如果返回非0，表示编程失败 */
        if(state != CFE_OK)
        {
            ENABLE_INT(); //开启全局中断
            BLT_LOG_INFO("write fail!%d",state);
            break;
        }
        ENABLE_INT(); //开启全局中断
        /* 显示复制进度 */
        if(count_of_buff == 0)
        {//百分之0的进度条
            sprintf(str_buff,"[          ]%03d%%", (uint8_t)(finish_pecent*100));
            TER_LOG_WITHOUT_NEWLINE("%s",str_buff);
        }
        TotalSize += bw;
        count_of_buff = count_of_buff + 1;
        finish_pecent = (float)(TotalSize) / (p_app_file->bin_size);
        BLT_LOG_INFO("current percent:%02d%%", (uint8_t)(finish_pecent*100));
        if((uint8_t)(finish_pecent*100) >= ter_pro_count)
        {
            TER_LOG_BACKSPACE(strlen((const char*)str_buff)); 
            sprintf(str_buff,"[          ]%03d%%", (uint8_t)(finish_pecent*100));
            for(uint8_t i = 1; i <= (ter_pro_count/10); i++) //绘制进度
                str_buff[i] = '=';
            TER_LOG_WITHOUT_NEWLINE("%s",str_buff);
            ter_pro_count += 10;
        }
    }
    // state = CPUFlash_UnlockLimit(APP_START_ADDRESS); //重新为上限枷锁
    f_lseek(&(p_app_file->app_file), 0); //每次读取完毕后都归位指针
    BLT_LOG_INFO("Load app finish!");
    BLT_LOG_INFO("=====================================");
    TER_LOG_INFO("Load app finish!");
    TER_LOG_INFO("=================");
    return res;
}

//*******************************外部函数************************************ */

/**
* @brief : void BLT_JumpToApp(void(*DeInitFunc)(uint8_t para))
* @param : void(*DeInitFunc)(uint8_t para) : BootLoader中用到的所有外设复位函数(一个uint8_t参数用于备用)
* @attention : 传入的是一个函数指针，传入的函数会在app跳转前调用，主要功能是关掉所有Bootloader中用到的外设
* @attention : 注意，这里跳转函数，并没有像正点原子一样，检查使用app的栈顶指针检查所用的SRAM是否超过SRAM的范围
*               如果需要可以在外界自己添加
* @author : Sheep
* @date : 24/10/22
*/
void BLT_JumpToApp(void(*DeInitFunc)(uint8_t para),uint8_t para)
{
	uint32_t i=0;
	void (*AppJump)(void);         /* 声明一个函数指针 */
    DeInitFunc(para); //执行外设的复位函数，为了保证跳转后外设再次初始化不出意外，这里需要将所有外设都复位关闭
    /* 关闭全局中断 */
	DISABLE_INT(); 
    /* 设置所有时钟到默认状态，使用HSI时钟 */
	HAL_RCC_DeInit();
	/* 关闭滴答定时器，复位到默认值 */
	SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;
	/* 关闭所有中断，清除所有中断挂起标志 */
	for (i = 0; i < 8; i++)
	{
		NVIC->ICER[i]=0xFFFFFFFF;
		NVIC->ICPR[i]=0xFFFFFFFF;
	}	
	/* 使能全局中断 */
	ENABLE_INT();
	/* 跳转到应用程序，首地址是MSP，地址+4是复位中断服务程序地址 */
	AppJump = (void (*)(void)) (*((uint32_t *) (APP_START_ADDRESS + 4)));
    //这一句将APP地址上的值(就是Reset_handler函数地址)取出，强转成函数指针，然后在下方调用
	/* 设置主堆栈指针 */
	__set_MSP(*(uint32_t *)APP_START_ADDRESS);
	/* 在RTOS工程，这条语句很重要，设置为特权级模式，使用MSP指针 */
	__set_CONTROL(0);
	/* 跳转到系统BootLoader */
	AppJump(); 
	/* 跳转成功的话，不会执行到这里，用户可以在这里添加代码 */
	while (1)
	{
	}
}

/**
* @brief : FRESULT BLT_CheckAppInfo(BLTAppFile *pAppFile)
* @param : pAppFile: 指向BLTAppFile结构体的指针,
* @attention : 注意会对传入的参数，检查bin_path、fs_built、fs_destruct 这三个必要的参数检查是否为NULL
*               同时会得到 bin_size、bin_version 的值
* @author : Sheep
* @date : 24/10/22
*/
FRESULT BLT_GetAppInfo(BLTAppFile *p_app_file)
{//调用装饰器调用内部函数
    return _BLT_AppFileDecorator(p_app_file,_BLT_GetAppInfo);
}

/**
* @brief : FRESULT BLT_GetAppCRC(BLTAppFile *p_app_file)
* @param : p_app_file : 指向 BLTAppFile 结构体的指针
* @attention : 注意会对传入的参数，检查bin_path、fs_built、fs_destruct 这三个必要的参数检查是否为NULL
*              还会检查 bin_size 大小的值，在使用这个函数前务必先使用getAppInfo获取固件大小
*              这个函数会取出两个CRC值，一个是固件自带的，另一个是由固件计算出来的
* @author : Sheep
* @date : 24/10/25
*/
FRESULT BLT_GetAppCRC(BLTAppFile *p_app_file)
{//调用装饰器调用内部函数
   return _BLT_AppFileDecorator(p_app_file,_BLT_GetAppCRC);
}

/**
* @brief : FRESULT BLT_LoadAppCheckCRC(BLTAppFile *pAppFile)
* @param : pAppFile: 指向BLTAppFile结构体的指针
* @attention : 注意会对传入的参数，检查bin_path、fs_built、fs_destruct 这三个必要的参数检查是否为NULL
*              还会检查 bin_size 大小的值，在使用这个函数前务必先使用getAppInfo获取固件大小
* @author : Sheep
* @date : 24/10/25
*/
FRESULT BLT_LoadApp(BLTAppFile *p_app_file)
{//调用装饰器调用内部函数
    return _BLT_AppFileDecorator(p_app_file,_BLT_LoadApp);
}

/**
* @brief : uint8_t BLT_APP_Is_Exist(void)
* @attention : 这个函数是用于重启时检验app是否存在，因为不清楚具体app的大小，因此不比较CRC
*               这个函数是为了烧写复位后可以直接运行app而作的检验，因此，没有校验CRC
* @return : 0:不存在，1:存在
* @author : Sheep
* @date : 24/10/22
*/
uint8_t BLT_APP_Is_Exist(void)
{
    uint32_t app_msp = *(volatile uint32_t*)(APP_START_ADDRESS);
    uint32_t app_version = *(volatile uint32_t*)(APP_START_ADDRESS+APP_VERSION_ADD_OFFSET);
    uint8_t res = 0; //默认不存在
    if(app_msp < 0x2000C000 && app_version != 0) //检查栈顶指针是否合法，因为RCT6只有48KB SRAM
        res = 1;
    //如果栈顶指针合法且app_ver不为0，我们就认为app已经烧写在Flash中了
    else res = 0;
    return res;
}


